package com.yifeng.repo.micro.service.server.engine.dal.ds;

import com.baomidou.dynamic.datasource.strategy.DynamicDataSourceStrategy;
import com.fasterxml.jackson.databind.JsonNode;
import com.gomcarter.frameworks.base.context.SpringContextHolder;
import com.google.common.base.Preconditions;
import com.yifeng.repo.base.utils.converter.JacksonHelper;
import com.yifeng.repo.micro.service.server.engine.configure.ServerEngineDaoProperties;
import com.yifeng.repo.micro.service.server.helper.WeightRandom;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Created by daibing on 2023/6/22.
 */
public class CompositeDynamicDataSourceStrategy implements DynamicDataSourceStrategy {
    private static final int REFRESH_INTERVAL_MILLIS = 3 * 60 * 1000;

    private final AtomicInteger dsRoutingTypeIndex = new AtomicInteger(0);
    private final AtomicLong dsRoutingTypeIndexLastRefreshTime = new AtomicLong(0L);
    private final ConcurrentMap<String, WeightRandom<String>> dsNamesKey2WeightRandom = new ConcurrentHashMap<>();

    /**
     * 负载均衡计数器
     */
    private final AtomicInteger loadBalanceIndex = new AtomicInteger(0);

    @Override
    public String determineKey(List<String> dsNames) {
        // 1. 入参检查
        if (dsNames == null || dsNames.size() == 0) {
            throw new RuntimeException("dynamic datasource is error!");
        }
        if (dsNames.size() == 1) {
            return dsNames.get(0);
        }

        // 2. 数据源路由类型编号：0. 负载均衡；1. 随机；2. 权重随机
        int dsRoutingTypeIndex = getDsRoutingTypeIndex();
        switch (dsRoutingTypeIndex) {
            case 0:
                return dsNames.get(Math.abs(loadBalanceIndex.getAndAdd(1) % dsNames.size()));
            case 1:
                return dsNames.get(ThreadLocalRandom.current().nextInt(dsNames.size()));
            case 2:
                return matchDsNameByWeightRandom(dsNames);
            default:
                throw new RuntimeException("dynamic datasource routing failed: " + dsRoutingTypeIndex);
        }
    }

    /**
     * 获取数据源路由类型编号：0. 负载均衡；1. 随机；2. 权重随机
     */
    private int getDsRoutingTypeIndex() {
        // 刷新间隔内就直接返回
        if (dsRoutingTypeIndexLastRefreshTime.get() + REFRESH_INTERVAL_MILLIS > System.currentTimeMillis()) {
            return dsRoutingTypeIndex.get();
        }

        // 刷新间隔外刷新配置
        ServerEngineDaoProperties properties = SpringContextHolder.getBean(ServerEngineDaoProperties.class);
        dsRoutingTypeIndex.set(properties.getDsRoutingType() < 0 ? 0 : Math.min(properties.getDsRoutingType(), 2));
        dsRoutingTypeIndexLastRefreshTime.set(System.currentTimeMillis());
        return dsRoutingTypeIndex.get();
    }

    /**
     * 根据权重随机匹配数据源名
     */
    private String matchDsNameByWeightRandom(List<String> dsNames) {
        // key
        String dsNamesKey = dsNames.stream().sorted().reduce((a, b) -> a + "," + b).orElse(null);
        Preconditions.checkNotNull(dsNamesKey, "dsNamesKey is null!");

        // 权重随机选择
        WeightRandom<String> weightRandom = dsNamesKey2WeightRandom.get(dsNamesKey);
        if (weightRandom != null && weightRandom.getTimestamp() + REFRESH_INTERVAL_MILLIS > System.currentTimeMillis()) {
            return weightRandom.choose();
        }
        synchronized (dsNamesKey2WeightRandom) {
            weightRandom = dsNamesKey2WeightRandom.get(dsNamesKey);
            if (weightRandom == null || weightRandom.getTimestamp() + REFRESH_INTERVAL_MILLIS < System.currentTimeMillis()) {
                weightRandom = buildWeightRandom(dsNames);
                dsNamesKey2WeightRandom.put(dsNamesKey, weightRandom);
            }
        }
        return weightRandom.choose();
    }

    private WeightRandom<String> buildWeightRandom(List<String> dsNames) {
        ServerEngineDaoProperties properties = SpringContextHolder.getBean(ServerEngineDaoProperties.class);
        List<WeightRandom.ItemWithWeight<String>> itemWithWeights = new ArrayList<>(dsNames.size());
        for (String dsName : dsNames) {
            Integer weight = properties.getDsRoutingPolicy().getOrDefault(dsName, 0);
            itemWithWeights.add(new WeightRandom.ItemWithWeight<>(dsName, weight));
        }
        return new WeightRandom<>(itemWithWeights);
    }

    public static void main(String[] args) {
        String dsRoutingPolicy = "{\"slave_1\":30,\"slave_2\":70}";
        JsonNode jsonNode = JacksonHelper.toObj(dsRoutingPolicy);
        List<String> dsNames = Arrays.asList("slave_1", "slave_2");
        dsNames.forEach(s -> System.out.println(jsonNode.get(s)));
        dsNames.stream().sorted().reduce((a, b) -> a + "," + b).ifPresent(System.out::println);

        String dsNamesKey = dsNames.stream().sorted().reduce((a, b) -> a + "," + b).orElse(null);
        System.out.println(dsNamesKey);
    }

}
